Skip to content

Rv64 m0 substrate - #38

Merged
adambagley merged 6 commits into
mainfrom
rv64-m0-substrate
Jul 30, 2026
Merged

Rv64 m0 substrate#38
adambagley merged 6 commits into
mainfrom
rv64-m0-substrate

Conversation

@adambagley

Copy link
Copy Markdown
Collaborator

No description provided.

Phase 1 planning artifacts, produced before any RTL change:

- docs/rv64/xlen_audit.md: point-in-time audit of main@9b76e39 - 365
  file:line findings across RTL, verif, tests, sw, linux, and fpga (106
  silent-misbehavior hazards, 104 design, 31 policy, 124 mechanical),
  three adversarially-verified readiness claims, and empirical
  tool-behavior results (zero-width replication accepted by every pinned
  tool; container gcc is rv64-capable; Spike absent from the image; rv64
  arch-test references collide with rv32 goldens today).

  Headline correction to the roadmap premise: FLD/FSD do not imply an
  8-byte memory path. The tier below the queues is 32-bit per
  transaction (two-phase FSMs, word-granular AMO/reservation, lo/hi
  CLINT), so a native 64-bit data tier is Phase 1 scope - and a phased
  64-bit read of the rolling mtime would tear, so it is not optional.

- docs/rv64/phase1_plan.md: strategy, decisions D1-D15, milestones
  M0-M8 with acceptance gates. Spine: XLEN behind a FROST_RV64 build
  define with the package as single source of truth; the 64-bit data
  tier lands first and is proven under the rv32 suites; addresses are
  canonicalized to the sub-4-GiB map at producers (signature-safe vs
  Spike because suite code forms addresses PC-relatively - argument
  recorded in D3); dual-XLEN CI through the phase with the keep/freeze
  decision measured at exit; Spike pinned into the container;
  mstatus.FS gets a real implementation including the FS=Off trap (the
  pinned kernel's fstate_save keys on trap-time FS==Dirty, and the
  machine-mode csr test requires Off to trap or suppress).

  Load-bearing decisions were cross-checked by independent review
  against the vendored kernel, Buildroot, and test-suite sources; the
  review confirmed the FS mechanism and the rv64-nommu BINFMT_FLAT
  userspace path, and sharpened D3's conformance argument.

- ROADMAP.md: Phase 0 marked done; Phase 1 marked current, with the
  audit's data-tier correction folded into its premise and pointers to
  the new docs.
…s, D3 masks

Behavior-preserving parameterization pass over the Phase 1 audit's hazard
inventory (docs/rv64/xlen_audit.md), all provably bit-identical at rv32:

- riscv_pkg: XLEN selected by the FROST_RV64 build define (plan D1);
  interrupt causes rebuilt XLEN-wide with the mcause interrupt bit at
  XLEN-1; exception causes widened; XLEN-wide DIV/REM special-case
  constants (SignedIntMin/SignedIntMax/UnsignedIntMax/NegativeOne)
  replacing the 32-bit-only forms; from_id_to_ex_t immediates widened to
  [XLEN-1:0] (D5); physical-map constants + canonical_paddr() (D3).
- Sign-extension replication counts made XLEN-relative in
  immediate_decoder (all five formats; U-type gains the RV64 bit-31
  replication) and pd_stage's two redirect immediates - the audit's
  highest-blast-radius silent-hazard class.
- D3 producer canonicalization: PC flops (pc_controller), resolved
  branch/JALR targets (branch_jump_unit), trap/mret redirect (trap_unit),
  and all eight store/issue AGU adder outputs. Identity at rv32; at
  XLEN=64 they pin bits [63:32] of fetch/memory addresses to zero so
  downstream PC/address state stays sub-4-GiB and synthesis sweeps the
  upper bits.
- Region decodes moved to fixed physical bits: if_stage's served-window
  guard (bit 31 via CachedRegionBit - [XLEN-1] goes dead at 64 and would
  resurrect the workqueue_init_early desync class) and lq_l0_cache's
  MMIO quadrant decode plus its formal properties.
- Module XLEN parameter defaults follow riscv_pkg::XLEN (24 modules) so
  standalone unit benches cannot diverge from the package; cpu_and_mem.f
  reorders cached_tier_adapter after the package (Yosys resolves package
  references in parameter defaults only if the package parses first).
- Elaboration-legality fixes for XLEN=64 with identical rv32 netlists:
  int-parameter part-selects become XLEN'() casts (load_queue, request
  router), [FLEN-1:XLEN] null ranges become shifts (load_queue,
  sq_forwarding_unit), fp_convert's FMV move slice gains a min-width
  guard, alu/branch_jump_unit immediate ports widen with the struct
  fields, instruction_type_decoder compares against '0.
- cpu_tb taps re-expressed via riscv_pkg::XLEN; FROST_XILINX_PRIMS FDRE
  loop bound by $bits; retire-trace format XLEN-selected (rv32 output
  byte-identical).

Gate: hello_world, coremark, directed_traps, c_ext_test, umode_test,
reservation_station, pd_stage, id_stage, if_stage pass at rv32; Yosys
generic passes at rv32. With FROST_RV64=1 the full tree compiles under
Verilator with warnings only (all mapping to audit-known Phase 1 work
sites) and passes Yosys generic coarse synthesis end to end.
- tests/Makefile: FROST_RV64=1 adds -DFROST_RV64 to the Verilator build;
  test_run_yosys.py mirrors it for every Yosys target, keyed on the same
  environment variable. The formal (.sby) and Vivado entry points gain
  the define when their milestones first need a 64-bit build (M5/M8 in
  docs/rv64/phase1_plan.md).
- verif/config.py becomes the Python-side single source of width truth:
  XLEN derives from the same FROST_RV64 environment variable (FLEN
  added alongside), so the reference models and struct packers flip in
  lockstep with the RTL. The 20 private "XLEN = 32" copies in cocotb
  interface/model/test files now import from config - a divergent copy
  can no longer silently misalign the hand-packed DUT struct layouts
  when the width changes.
Interface contract for the 64-bit single-beat data tier (plan D2/M1):
aligned-dword bus view with 8-lane strobes and replication-based write
positioning; dmem as one 64-bit BRAM with a new sw64.mem init (image and
loader formats stay 32-bit-word); CLINT gains single-copy-atomic 64-bit
mtime/mtimecmp access with the lo/hi aliases preserved; LQ/SQ two-phase
FLD/FSD machinery is deleted (single-beat doubles join drain
pipelining); forwarding moves to dword granule; L0 goes to dword lines.
Includes the deletion list, the rv32 gate list, and the doc blast
radius.
Implement docs/rv64/m1_data_tier.md: the data-memory tier becomes
aligned-dword single-beat (MemDataBits=64, 8-lane strobes, replication
write positioning) while the core stays rv32 — every widened path is
proven by the existing rv32 suites. The FLD/FSD two-phase machinery is
deleted, not generalized: phased dwords cannot serve RV64 (torn mtime,
unphaseable AMO*.D), and the un-gated size-keyed paths are exactly what
RV64 LD/SD reuse in M3.

Memory side: router/adapter/dmem/MMIO carry MemDataBits beats; the data
BRAM is one 64-bit byte-enabled BRAM at half depth initialized from a
new sw64.mem (dword-paired copy emitted next to sw.mem by every software
build backend; all loader/JTAG formats stay 32-bit-word). The CLINT's
dword-aligned mtime/mtimecmp gain single-copy-atomic 64-bit access;
UART/FIFO registers stay 32-bit-max. AMO writes present {2{result}}
with word-lane strobes derived from addr[2].

Queue side: the SQ drains every size in one beat (gen_byte_en 8-lane
via riscv_pkg::mem_strobe_for, gen_write_data replication, sq_fp64_phase
excised, DOUBLE joins the pipelined fast drain). The LQ merges its
split lo/hi data RAMs into one FLEN-wide 2-port LUTRAM keeping today's
value semantics (DOUBLE = the full beat; others = extracted value
zero-extended; NaN-boxing at CDB broadcast), deletes issued_fp64_phase,
the +4 issue leg and re-issue/phase-advance arms, un-gates the
is_fp&&DOUBLE conjuncts to size-only tests, and selects the AMO old
value from the beat by addr[2]. load_unit extracts word/half/byte from
the beat by addr[2:0]. The L0 moves to dword-granule lines with
physical-width tags (FLD becomes L0-eligible); the in-flight-fill
invalidation snoop widens to dword granule. sq_forwarding_unit's
overlap model moves to dword granule — same-dword + 8-lane mask
intersection is exact (no access crosses its beat; misaligned accesses
trap upstream), covered-subset forwarding subsumes the old case-1..3
matrix, the double_hi/word_addr_inc comparators and the extract-type
machinery are deleted, and the forwarded payload is the aligned-dword
memory image (store data shifted to its lanes). A single-beat FSD now
fires ONE store-launch invalidate, so the SQ exports is_dword and the
wrapper reservation snoop widens its compare to the dword for
dword-covering stores — exactly the coverage the old two-pulse drain
delivered (word stores keep the word-granule snoop).

Verif mirrors the bus contract end to end: config beat constants,
memory_utils 8-lane strobes + replication helpers, memory_model
dword-row init + 64-bit expected-write monitor + DUT row poke/peek
helpers, cpu_model/directed-test/wrapper-bench expected writes as
replicated beats, LQ/SQ unit benches (single-beat FLD/FSD, dword-image
forwarding, beat AMO writes; the FLD phase-reuse regression retires
with the phase machinery), cpu_tb's data memory at MemDataBits.
test_run_cocotb now fails fast on a missing program-memory image (a
dangling sw64.mem symlink previously left the data BRAM zero-filled
silently), and the yosys/riscv-tests/arch/torture runners stage
sw64.mem alongside sw.mem.

Local gate (all green, judged by PASS/FAIL text): rv32ud/rv32uf/rv32ua
riscv-tests (52/52); torture both tiers (20+20); fpu_assembly_test,
ddr_test, ddr_heap_test, ddr_atomic_test, ddr_smc_test, frost_cache
(13/13), restore_window_stress (unmodified), clint_test, isa_test,
memory_test, hello_world; directed atomics/traps/compressed;
load_queue (60/60), store_queue (53/53), tomasulo_wrapper (75/75 +
split-RS 8/8), tomasulo_test benches; LQ/SQ/L0 formal (bmc+cover)
within existing depth budgets; Verilator lint clean at rv32 and
warning-free in changed files at FROST_RV64=1; Yosys generic and
UltraScale+ synthesis; repo lint + fast Python checks. The arch F/D
compliance batches are left to CI's matrix. An independent review of
the value-semantics, forwarding-equivalence, reservation-snoop, and
L0-timing arguments found no counterexample.

Known pre-existing (unchanged by this commit): directed_multicycle and
cpu_random fail on main too — the registry marks them CLI-only, "NEEDS
PORTING to OOO"; their FLD/beat modeling is updated here so the future
port starts consistent.
…ux_boot sw64.mem

Three misses the local M1 gate did not cover:

- direction_predictor / prediction_metadata_tracker / control_flow_tracker
  unit benches elaborate their modules standalone, and since the M0
  substrate their parameter defaults reference riscv_pkg::XLEN — add
  riscv_pkg.sv to those three file lists in tests/Makefile (M0 fallout;
  these targets were not in the local M0 gate).

- test_data_mem_request_router still asserted 4-lane AMO strobes. The
  cached-AMO case (addr[2]=1) now correctly drives 0xF0; the BRAM-AMO
  case only passed because 0x0F equals the old 4-bit 0b1111. Both
  expectations are now explicit 8-lane values.

- The linux_boot app Makefile (its own two-mode build, not common.mk)
  never produced sw64.mem, so the runner's new missing-image guard
  correctly refused to stage a dangling symlink in CI. Both modes now
  derive sw64.mem from sw.mem (in FROST_LINUX_PREBUILT=1 mode it is a
  pure re-encoding of the staged authoritative image), and clean targets
  cover it.

Verified locally: all four benches pass (11/11, 6/6, 9/9, 7/7); a
prebuilt-mode make derives sw64.mem from the staged image; a linux_boot
smoke run boots to the 'Linux version' banner at ~2.1M cycles.
@adambagley
adambagley merged commit 1920c69 into main Jul 30, 2026
157 of 158 checks passed
@adambagley
adambagley deleted the rv64-m0-substrate branch July 30, 2026 13:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant